﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Collections;
using System.IO;
using Microsoft.Win32;
using System.Threading.Tasks;

namespace SvnBlamer
{
    public partial class Form1 : Form
    {
        private const string MenuName = "*\\shell\\SvnBlamer";
        private const string Command = "*\\shell\\SvnBlamer\\command";
        public static void AddToExplorerMenu()
        {
            RegistryKey regmenu = null;
            RegistryKey regcmd = null;
            try
            {
                regmenu = Registry.ClassesRoot.CreateSubKey(MenuName);
                if (regmenu != null)
                    regmenu.SetValue("", "Prog-NET Svn &Blamer");
                regcmd = Registry.ClassesRoot.CreateSubKey(Command);
                if (regcmd != null)
                    regcmd.SetValue("", Application.ExecutablePath + " \"%1\"");
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
            finally
            {
                if (regmenu != null)
                    regmenu.Close();
                if (regcmd != null)
                    regcmd.Close();
            }
        }
        public static void RemoveFromExplorerMenu()
        {
            try
            {
                RegistryKey reg = Registry.ClassesRoot.OpenSubKey(Command);
                if (reg != null)
                {
                    reg.Close();
                    Registry.ClassesRoot.DeleteSubKey(Command);
                }
                reg = Registry.ClassesRoot.OpenSubKey(MenuName);
                if (reg != null)
                {
                    reg.Close();
                    Registry.ClassesRoot.DeleteSubKey(MenuName);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }
        public static bool IsAddedToMenu()
        {
            bool isAddedToExplorer = false;
            try
            {
                RegistryKey reg = Registry.ClassesRoot.OpenSubKey(Command);
                if (reg != null)
                {
                    isAddedToExplorer = true;
                    reg.Close();
                }
            }
            catch { }
            return isAddedToExplorer;
        }
        
        private bool isAddedToExplorer;

        private string file;
        private string Pattern;
        private bool UseRegEx;
        private bool IgnoreCase;
        private string SvnExe;
        private bool wrap;
        private bool stopOnCopyRename;
        private bool IgnoreLineEndingDiff;

        List<RevisionInfo> revisions = new List<RevisionInfo>();
        List<RevisionInfo> tmpRev = new List<RevisionInfo>();

        private string AppDataFile;

        public Form1()
        {
            InitializeComponent();
        }
        public Form1(string file)
        {
            InitializeComponent();
            this.Text = Application.ProductName;

            AppDataFile = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\SvnBlamer.ini";
            ReadIni();
            this.file = file;
            teFile.Text = file;
            isAddedToExplorer = IsAddedToMenu();
            SetMenu();
        }
        private void SetMenu()
        {
            if (isAddedToExplorer)
            {
                btnAddToMenu.Text = "Remove From Explorer Menu";
            }
            else
            {
                btnAddToMenu.Text = "Add To Explorer Menu";
            }
        }
        private void ReadIni()
        {
            try
            {
                this.teSvnExe.Text = Ext.GetDefaultTortoiseSvnPath();
                this.teTortoiseProcExe.Text = Ext.GetDefaultTortoiseProcPath();

                string ini = File.ReadAllText(AppDataFile);
                string value = ini.GetFirstMatch("^TortoiseProc=(.*)$", 1, RegexOptions.IgnoreCase | RegexOptions.Multiline);
                if (!value.IsNull())
                    this.teTortoiseProcExe.Text = value.Trim();

                value = ini.GetFirstMatch("^SvnExe=(.*)$", 1, RegexOptions.IgnoreCase | RegexOptions.Multiline);
                if (!value.IsNull())
                    this.teSvnExe.Text = value.Trim();

                value = ini.GetFirstMatch("^WrapLines=(.*)$", 1, RegexOptions.IgnoreCase | RegexOptions.Multiline);
                if (!value.IsNull())
                    bool.TryParse(value, out wrap);

                value = ini.GetFirstMatch("^StopOnCopyRename=(.*)$", 1, RegexOptions.IgnoreCase | RegexOptions.Multiline);
                if (!value.IsNull())
                    bool.TryParse(value, out stopOnCopyRename);
                ceStopOnCopy.Checked = stopOnCopyRename;

                value = ini.GetFirstMatch("^IgnoreLineEndingDiff=(.*)$", 1, RegexOptions.IgnoreCase | RegexOptions.Multiline);
                if (!value.IsNull())
                    bool.TryParse(value, out IgnoreLineEndingDiff);
                ceIgnoreLineEndings.Checked = IgnoreLineEndingDiff;

                value = ini.GetFirstMatch("^IgnoreCase=(.*)$", 1, RegexOptions.IgnoreCase | RegexOptions.Multiline);
                if (!value.IsNull())
                    bool.TryParse(value, out IgnoreCase);
                ceIgnoreCase.Checked = IgnoreCase;

                value = ini.GetFirstMatch("^UseRegEx=(.*)$", 1, RegexOptions.IgnoreCase | RegexOptions.Multiline);
                if (!value.IsNull())
                    bool.TryParse(value, out UseRegEx);
                ceRegEx.Checked = UseRegEx;
            }
            catch { }
        }

        public void WriteIni()
        {
            try
            {
                StringBuilder sb = new StringBuilder();
                sb.AppendLine(string.Format("TortoiseProc={0}", teTortoiseProcExe.Text));
                sb.AppendLine(string.Format("SvnExe={0}", teSvnExe.Text));
                sb.AppendLine(string.Format("WrapLines={0}",wrap));
                sb.AppendLine(string.Format("StopOnCopyRename={0}", ceStopOnCopy.Checked));
                sb.AppendLine(string.Format("IgnoreLineEndingDiff={0}", ceIgnoreLineEndings.Checked));
                sb.AppendLine(string.Format("IgnoreCase={0}", ceIgnoreCase.Checked));
                sb.AppendLine(string.Format("UseRegEx={0}", ceRegEx.Checked));
                File.WriteAllText(AppDataFile, sb.ToString());
            }
            catch { }
        }
        private bool cancel = false;

        private bool RevisionsRead = false;
        private bool OperationInProgress = false;

        private void ReadRevisions()
        {
            RevisionsRead = false;
            using (Process searchProc = CreateProcess())
            {
                cancel = false;
                tmpRev.Clear();
                allInfo.Clear();
                revisions.Clear();
                this.IgnoreLineEndingDiff = ceIgnoreLineEndings.Checked;
                this.IgnoreCase = ceIgnoreCase.Checked;
                stopOnCopyRename = ceStopOnCopy.Checked;
                this.Pattern = teSearchFor.Text;
                if (IgnoreCase)
                    Pattern = Pattern.ToUpper();
                this.UseRegEx = this.ceRegEx.Checked;

                string startRev = "1";
                string endRev = "HEAD";

                if (ceStartRev.Checked)
                {
                    if (!teStartRev.Text.IsNull())
                    {
                        int i = 1;
                        if (int.TryParse(teStartRev.Text, out i) && i > 0)
                            startRev = i.ToString();
                    }
                }
                if (ceEndRev.Checked)
                {
                    if (!teEndREv.Text.IsNull())
                    {
                        int i = 1;
                        if (int.TryParse(teEndREv.Text, out i) && i > 0)
                            endRev = i.ToString();
                    }
                }
                string svnexe = "svn.exe";

                if (!teSvnExe.Text.IsNull())
                    svnexe = teSvnExe.Text;
                SvnExe = svnexe;
                string parameters = string.Empty;
                if (stopOnCopyRename)
                    parameters = "--stop-on-copy";
               
                searchProc.Execute(string.Format("\"{0}\" log \"{1}\" -q {4} -r {2}:{3}", svnexe, file, startRev, endRev, parameters));
                searchProc.Echo("Exit");

                try
                {
                    while (!cancel && !searchProc.HasExited)
                    {
                        System.Threading.Thread.Sleep(10);
                    }
                }
                catch { }
            }
            
            RevisionsRead = true;
        }

        private void btnSearch_Click(object sender, EventArgs e)
        {
            if (!OperationInProgress)
            {
                file = teFile.Text;
                try
                {
                    if (file.IsNull())
                    {
                        MessageBox.Show("Please select file.", Application.ProductName);
                        return;
                    }
                    if (!File.Exists(file))
                    {
                        MessageBox.Show("File does not exist.", Application.ProductName);
                        return;
                    }
                }
                catch
                {
                    MessageBox.Show("Invalid file.", Application.ProductName);
                    return;
                }
                OperationInProgress = true;
                try
                {
                    errorProvider1.SetError(this.teSearchFor, string.Empty);
                    lblError.Text = string.Empty;
                    this.btnShowErrors.Enabled = true;
                    this.revisionInfoBindingSource.DataSource = null;
                    lblInfo.Text = "Found revisions:";

                    string pattern = teSearchFor.Text.AsString();
                    bool useRegEx = ceRegEx.Checked;
                    if (pattern.Trim().IsNull())
                    {
                        errorProvider1.SetError(teSearchFor, "Enter search pattern");
                        return;
                    }
                    if (useRegEx)
                    {
                        if (!pattern.IsValidRegex())
                        {
                            errorProvider1.SetError(teSearchFor, "Invalid regular expression patetrn");
                            return;
                        }
                    }
                    RevisionsRead = false;
                    cancel = false;
                    Task t = Task.Factory.StartNew(ReadRevisions);
                    while (!RevisionsRead && !cancel)
                    {
                        Application.DoEvents();
                    }

                    lblInfo.Text = "Searching revisions:";
                    lblInfo2.Text = "";
                    RevisionsRead = false;
                    Task t2 = Task.Factory.StartNew(GetCorrectRevisions);
                }
                catch { }
            }
            else
            {
                MessageBox.Show(this, "Search in progress.", this.ProductName, MessageBoxButtons.OK);
            }
        }
        private void Finish()
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new Action(Finish));
                return;
            }
            RevisionsRead = true;
            lblInfo.Text = "Search finished";
            lblInfo2.Text = "";

            this.revisionInfoBindingSource.DataSource = revisions;
            OperationInProgress = false;
        }
        private void GetCorrectRevisions()
        {
            Process pr = CreateProcess(GetRevInfo);
            idx = 0;
            currentRev = string.Empty;

            try
            {
                RevisionsRead = false;
                if (!cancel)
                {
                    foreach (RevisionInfo r in tmpRev)
                    {
                        if (cancel)
                            break;
                        if (!allInfo.ContainsKey(r.Revision))
                        {
                            allInfo.Add(r.Revision, r);
                        }
                    }
                    string parameters = string.Empty;
                    if (IgnoreLineEndingDiff)
                        parameters += " -x \" -w --ignore-eol-style\"";
                    lock (allInfo)
                    {
                        foreach (var kvp in allInfo)
                        {
                            if (cancel)
                                break;
                            pr.Echo("Revision:" + kvp.Key);
                            System.Threading.Thread.Sleep(10);
                            pr.Execute(string.Format("\"{0}\" diff \"{1}\" {3} -c {2}", SvnExe, file, kvp.Key, parameters));
                            System.Threading.Thread.Sleep(10);
                        }
                    }
                }
            }
            catch
            {

            }
            finally
            {
                pr.Echo("Exit");
            }
        }
        private void SetRevSearchCount(int idx, int max)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new Action<int, int>(SetRevSearchCount), idx, max);
                return;
            }
            lblInfo2.Text = idx + "/" + max;
        }
        protected override void OnFormClosing(FormClosingEventArgs e)
        {
            cancel = true;
            base.OnFormClosing(e);
            WriteIni();
        }
        private Process CreateProcess(Action<object, DataReceivedEventArgs> handler = null)
        {
            Process process = new Process();
            ProcessStartInfo psi = new ProcessStartInfo("cmd.exe");
            psi.CreateNoWindow = true;
            psi.UseShellExecute = false;
            psi.RedirectStandardError = true;
            psi.RedirectStandardInput = true;
            psi.RedirectStandardOutput = true;
            psi.WindowStyle = ProcessWindowStyle.Hidden;
            process.StartInfo = psi;

            if (handler != null)
            {
                process.OutputDataReceived += new DataReceivedEventHandler(handler);
            }
            else
            {
                process.OutputDataReceived += new DataReceivedEventHandler(process_ErrorDataReceived);
            }
            process.ErrorDataReceived += new DataReceivedEventHandler(ErrorDataReceived);
                
            process.StartInfo = psi;
            process.Start();
            process.BeginErrorReadLine();
            process.BeginOutputReadLine();
            process.EchoOff();
            return process;
        }

        void process_ErrorDataReceived(object sender, DataReceivedEventArgs e)
        {
            Process p = (Process)sender;
            try
            {
                if (cancel)
                    return;
                string text = e.Data.AsString();
                if (text.IsNull())
                    return;
                if (text.XMatch("^Exit$"))
                {
                    p.Execute("exit");
                    return;
                }
                Dictionary<int, string> d = text.GetFirstMatch("^r(\\d*) \\| (.*?) \\| (.*)$", new int[] { 1, 2, 3 });
                if (d != null)
                {
                    RevisionInfo r = new RevisionInfo();
                    r.Revision = d[1];
                    r.Author = d[2];
                    r.Date = d[3];
                    AddRev(r);
                }
            }
            catch { }
        }
        void ErrorDataReceived(object sender, DataReceivedEventArgs e)
        {
            if (cancel)
                return;
            Process p = sender as Process;
            try
            {
                string text = e.Data.AsString();
                if (text.IsNull())
                    return;
                SetError(text);
            }
            catch { }
        }
        private void SetError(string text)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new Action<string>(SetError), text);
                return;
            }
            lblError.Text += text;
            this.btnShowErrors.Enabled = true;
        }
        private void AddRev(RevisionInfo r)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new Action<RevisionInfo>(AddRev), r);
                return;
            }
            tmpRev.Add(r);
            lblInfo2.Text = tmpRev.Count.ToString();
        }

        private string currentRev;
        private int idx = 0;
        private List<string> addedRev = new List<string>();

        private void GetRevInfo(object sender, DataReceivedEventArgs e)
        {
            Process p = (Process)sender;
            string text = e.Data.AsString();
            if (text.IsNull())
                return;
            if (text.XMatch("^Exit$"))
            {
                try
                {
                    p.Execute("exit");
                    p.Dispose();
                }
                catch { }
                finally
                {
                    Finish();
                }
                return;
            }
            if (cancel)
                return;

            if (text.XMatch("^Revision:(\\d*)$"))
            {
                SetRevSearchCount(idx++, tmpRev.Count);
                currentRev = text.GetFirstMatch("^Revision:(\\d*)$");
                return;
            }
            if (!currentRev.IsNull())
            {
                if (allInfo.ContainsKey(currentRev))
                {
                    if (revisions.Contains(allInfo[currentRev]))
                        return;

                    string mask = "^-|\\+\\s+";
                    if (text.XMatch(mask))
                    {
                        if (UseRegEx)
                        {
                            RegexOptions o = RegexOptions.Singleline;
                            if (IgnoreCase)
                                o |= RegexOptions.IgnoreCase;
                            if (text.XMatch(Pattern, o))
                                revisions.Add(allInfo[currentRev]);
                        }
                        else
                        {
                            if (IgnoreCase)
                                text = text.ToUpper();

                            if (text.Contains(Pattern))
                                revisions.Add(allInfo[currentRev]);
                        }
                    }
                }
            }
        }

        private Dictionary<string, RevisionInfo> allInfo = new Dictionary<string, RevisionInfo>();

        private void dataGridView1_CellClick(object sender, DataGridViewCellEventArgs e)
        {
            if (e.RowIndex < 0 || e.ColumnIndex != dataGridView1.Columns.IndexOf(colShowLog))
                return;
            DataGridViewRow r = dataGridView1.Rows[e.RowIndex];
            if (r != null)
            {
                if (r.DataBoundItem is RevisionInfo)
                {
                    string tortoise = teTortoiseProcExe.Text;
                    try
                    {
                        if (File.Exists(tortoise))
                        {
                            string param = "";

                            param = string.Format("/command:log /merge /findtype:8 /findstring:\"{1}\" /path:\"{0}\" /closeonend:0", file, ((RevisionInfo)r.DataBoundItem).Revision);
                            Process.Start(tortoise, param);
                        }
                        else
                        {
                            MessageBox.Show("TortoiseProc.exe does not exist.");
                        }
                    }
                    catch { }
                }
            }
        }

        private void btnAddToMenu_Click(object sender, EventArgs e)
        {
            if (isAddedToExplorer)
            {
                RemoveFromExplorerMenu();
            }
            else
            {
                AddToExplorerMenu();
            }
            isAddedToExplorer = !isAddedToExplorer;
            SetMenu();
        }

        private void btnCancel_Click(object sender, EventArgs e)
        {
            cancel = true;
            allInfo.Clear();
        }

        private void teSearchFor_TextChanged(object sender, EventArgs e)
        {
            errorProvider1.SetError(this.teSearchFor, string.Empty);
            Clear();
        }

        private void ceRegEx_CheckedChanged(object sender, EventArgs e)
        {
            errorProvider1.SetError(this.teSearchFor, string.Empty);
        }

        private void btnShowErrors_Click(object sender, EventArgs e)
        {
            if (lblError.Text.IsNull())
                return;

            using (FormError f = new FormError(lblError.Text, wrap))
            {
                f.ShowDialog();
                wrap = f.Wrap;
            }
        }

        private void btnBrowseSvnExe_Click(object sender, EventArgs e)
        {
            string oldFile = teSvnExe.Text;
            string filename = string.Empty;
            string path = string.Empty;
            try
            {
                path = Path.GetDirectoryName(oldFile);
                filename = Path.GetFileName(oldFile);
            }
            catch
            {
            }
            if (filename.IsNull())
                filename = "svn.exe";
            openFileDialog1.Filter = "Executables|*.exe";
            openFileDialog1.FileName = filename;
            openFileDialog1.InitialDirectory = path;
            if (openFileDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                teSvnExe.Text = openFileDialog1.FileName;
        }

        private void btnBrowseTortoiseProcExe_Click(object sender, EventArgs e)
        {
            string oldFile = teTortoiseProcExe.Text;
            string filename = string.Empty;
            string path = string.Empty;
            try
            {
                path = Path.GetDirectoryName(oldFile);
                filename = Path.GetFileName(oldFile);
            }
            catch
            {
            }
            if (filename.IsNull())
                filename = "TortoiseProc.exe";
            openFileDialog1.Filter = "Executables|*.exe";
            openFileDialog1.FileName = filename;
            openFileDialog1.InitialDirectory = path;
            if (openFileDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                teTortoiseProcExe.Text = openFileDialog1.FileName;
        }

        private void btnFile_Click(object sender, EventArgs e)
        {
            string oldFile = teFile.Text;
            string filename = string.Empty;
            string path = string.Empty;
            try
            {
                path = Path.GetDirectoryName(oldFile);
                filename = Path.GetFileName(oldFile);
            }
            catch
            {
            }
            openFileDialog1.Filter = "All Files|*.*";
            openFileDialog1.FileName = filename;
            openFileDialog1.InitialDirectory = path;
            if (openFileDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                teFile.Text = openFileDialog1.FileName;
        }

        private void teFile_TextChanged(object sender, EventArgs e)
        {
            Clear();
        }

        private void Clear()
        {
            if (OperationInProgress)
                cancel = true;
            lblError.Text = string.Empty;
            revisionInfoBindingSource.DataSource = null;
            revisions.Clear();
        }
    }

    public class RevisionInfo
    {
        public String Revision { get; set; }
        public string Author { get; set; }
        public string Date { get; set; }
        public override string ToString()
        {
            return Revision;
        }
    }
    public static class Ext
    {
        public static void SetAsDefaultFormIcon(this Icon ico)
        {
            System.Reflection.FieldInfo fi = typeof(System.Windows.Forms.Form).GetField("defaultIcon", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
            if (fi != null)
                fi.SetValue(null, ico);
        }


        public static string GetDefaultTortoiseProcPath()
        {
            string str = "";
            try
            {
                str = Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\TortoiseSVN", "ProcPath", "").AsString();
                if (str.IsNull() || !File.Exists(str))
                    str = "";
            }
            catch { str = ""; }
            return str;
        }
        public static string GetDefaultTortoiseSvnPath()
        {
            string str = "";
            try
            {
                str = Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\TortoiseSVN", "Directory", "").AsString();
                if (str.IsNull() || !Directory.Exists(str))
                    str = "";
                else
                {
                    str = str.TrimEnd('\\') + "\\bin\\svn.exe";
                    if (!File.Exists(str))
                        str = "";
                }
            }
            catch { str = ""; }
            return str;
        }
        #region Regular expresions
        public static bool IsValidRegex(this string pattern)
        {
            if (string.IsNullOrEmpty(pattern)) return false;

            try
            {
                Regex.Match("", pattern);
            }
            catch (ArgumentException)
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// Gets list of matches
        /// </summary>
        /// <param name="value"></param>
        /// <param name="pattern">Pattern</param>
        /// <param name="groups">groups to return</param>
        /// <param name="options">RegexOptions</param>
        /// <returns></returns>
        public static List<Dictionary<int, string>> GetMatches(this string value, string pattern, int[] groups, RegexOptions options = RegexOptions.IgnoreCase)
        {
            List<Dictionary<int, string>> list = new List<Dictionary<int, string>>();
            if (groups != null)
            {
                MatchCollection coll = value.XMatches(pattern, options);
                Dictionary<int, string> dic = null;
                foreach (Match m in coll)
                {
                    dic = new Dictionary<int, string>();
                    list.Add(dic);
                    foreach (int group in groups)
                    {
                        dic.Add(group, m.Groups[group].Value);
                    }
                }
            }
            return list;
        }

        /// <summary>
        /// Gets list of matches for single group
        /// </summary>
        /// <param name="value"></param>
        /// <param name="pattern">Pattern</param>
        /// <param name="group">Group</param>
        /// <param name="options">RegexOptions</param>
        /// <returns></returns>
        public static List<string> GetMatches(this string value, string pattern, int group = 1, RegexOptions options = RegexOptions.IgnoreCase)
        {
            List<string> list = new List<string>();
            List<Dictionary<int, string>> l = value.GetMatches(pattern, new int[] { group }, options);
            foreach (Dictionary<int, string> d in l)
            {
                list.AddRange(d.Values);
            }
            return list;
        }

        /// <summary>
        /// Gets first match for some groups. null is returned if value does not match pattern
        /// </summary>
        /// <param name="value"></param>
        /// <param name="pattern">Pattern</param>
        /// <param name="groups">Groups</param>
        /// <param name="options">RegexOptions</param>
        /// <returns></returns>
        public static Dictionary<int, string> GetFirstMatch(this string value, string pattern, int[] groups, RegexOptions options = RegexOptions.IgnoreCase)
        {
            List<Dictionary<int, string>> l = value.GetMatches(pattern, groups, options);
            if (l.Count > 0)
                return l[0];
            return null;
        }

        /// <summary>
        /// Gens forst match
        /// </summary>
        /// <param name="value"></param>
        /// <param name="pattern">Pattern</param>
        /// <param name="group">Group</param>
        /// <param name="options">RegexOptions</param>
        /// <returns></returns>
        public static string GetFirstMatch(this string value, string pattern, int group = 1, RegexOptions options = RegexOptions.IgnoreCase)
        {
            Dictionary<int, string> d = value.GetFirstMatch(pattern, new int[] { group }, options);
            if (d != null)
                return d[group];
            return string.Empty;
        }

        /// <summary>
        /// Gets matches
        /// </summary>
        /// <param name="value"></param>
        /// <param name="pattern">Pattern</param>
        /// <param name="options">RegexOptions</param>
        /// <returns></returns>
        public static MatchCollection XMatches(this string value, string pattern, RegexOptions options = RegexOptions.IgnoreCase)
        {
            return Regex.Matches(value, pattern, options);
        }

        /// <summary>
        /// Check if string matches pattern
        /// </summary>
        /// <param name="value"></param>
        /// <param name="pattern"></param>
        /// <param name="options"></param>
        /// <returns></returns>
        public static bool XMatch(this string value, string pattern, System.Text.RegularExpressions.RegexOptions options = RegexOptions.IgnoreCase)
        {
            bool result = false;
            try
            {
                result = Regex.IsMatch(value, pattern, options);
            }
            catch { }
            return result;
        }
        /// <summary>
        /// Splits string        
        /// </summary>
        /// <param name="value"></param>
        /// <param name="pattern"></param>
        /// <param name="options"></param>
        /// <returns></returns>
        public static string[] XSplit(this string value, string pattern, RegexOptions options = RegexOptions.IgnoreCase)
        {
            return Regex.Split(value, pattern, options);
        }


        /// <summary>
        /// Replaces string with another string
        /// </summary>
        /// <param name="value"></param>
        /// <param name="pattern"></param>
        /// <param name="replacement"></param>
        /// <param name="options"></param>
        /// <returns></returns>
        public static string XReplace(this string value, string pattern, string replacement, RegexOptions options = RegexOptions.IgnoreCase)
        {
            return Regex.Replace(value, pattern, replacement, options);
        }

        private static string[] specials = new string[]{
                        "\\","/", ".", "*", "+", "?", "|",
                        "(", ")", "[", "]", "{", "}", 
                        "$", "^"
        };

        /// <summary>
        /// Escapes special chars for Regular Expressions
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static string XEscape(this string value)
        {
            if (value != null)
            {
                foreach (string s in specials)
                {
                    value = value.Replace(s, "\\" + s);
                }
            }
            return value;
        }
        /// <summary>
        /// Formats string by replacing {x} with object from args.
        /// x starts with 0
        /// </summary>
        /// <param name="value"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        public static string XFormat(this string value, params object[] args)
        {
            if (args != null)
            {
                int i = 0;
                foreach (object o in args)
                {
                    value = value.XReplace(@"\{\s*" + i + @"\s*\}", o.AsString());
                    i++;
                }
            }
            return value;
        }
        #endregion

        public static bool IsNull(this object value, bool DBNull = true)
        {
            if (value == null)
                return true;
            if (DBNull && value == System.DBNull.Value)
                return true;
            if (value.ToString().Trim() == string.Empty)
                return true;
            if (value is Array)
                return (value as Array).Length == 0;
            return false;
        }
        public static string AsString(this object value)
        {
            if (value.IsNull())
                return string.Empty;
            if (value is string)
                return (string)value;
            if (value is IEnumerable)
                return (value as IEnumerable).AsString();
            return Convert.ToString(value);
        }
        public static string AsString(this IEnumerable value, string separator = ", ")
        {
            if (value.IsNull())
                return string.Empty;
            if (value is string)
                return (string)value;
            if (value is byte[])
                return ((byte[])value).ByteArrayToString();
            StringBuilder str = new StringBuilder();
            foreach (object obj in value)
            {
                if (str.Length > 0)
                    str.Append(separator);
                str.Append(obj.AsString());
            }
            return str.ToString();
        }
        public static string ByteArrayToString(this byte[] bytes, Encoding enc)
        {
            return enc.GetString(bytes);
        }
        public static string ByteArrayToString(this byte[] bytes)
        {
            return bytes.ByteArrayToString(Encoding.UTF8);
        }
        public static void EchoOff(this Process pr)
        {
            pr.Execute("@echo off");
        }
        public static void Echo(this Process pr, string msg)
        {
            pr.Execute("echo " + msg);
        }

        public static void Execute(this Process pr, string str)
        {
            try
            {
                pr.StandardInput.WriteLine(str);
                pr.StandardInput.Flush();
            }
            catch { }
        }
    }
}
